//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2011 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_ITERATOR_FACADE_H_
#define _BUOLA_ITERATOR_FACADE_H_

#include <buola/buola.h>
#include <buola/iterator/traits.h>

namespace buola {

///\addtogroup iterators
///@{

///\cond
namespace detail {

    ///This class is used for input iterators where after post-incrementing, the previous iterator might not be
    ///valid anymore.
    template<typename tValue>
    class CInputIteratorPostfixHelper
    {
    public:
        template<typename tIterator>
        CInputIteratorPostfixHelper(const tIterator &pI)
            :   mValue(*pI)
        {}

        const tValue& operator*() const {   return mValue;  }

    private:
        tValue mValue;
    };

/*namespace detail*/ }
///\endcond

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/// iterator facade

/// This class, which is heavily based on boost's iterator_facade, helps with the implementation
/// of iterators.
///
/// It uses the Curiously-Recurring Template Pattern to adapt the interface of an iterator-like
/// object into a fully standard-compliant iterator. To do that, a derived class `CIterator`
/// must inherit from `IFacade<CIterator,...>`.
///
/// The derived iterator-like class must implement the following functions (only a subset of
/// them might need to be implemented, depending on the iterator category):
///
/// - `Dereference()` returns a reference to the value referred to by the iterator
/// - `Increment()` advances the iterator by one position
/// - `Decrement()` retreats the iterator by one position
/// - `Equal(j)` equality comparison with another iterator j
/// - `Advance(n)` advances the iterator by n positions
/// - `DistanceTo(j)` returns the distance to iterator j
///
/// The implementation of these functions allow IFacade to provide the interface of a
/// fully-standard-compatible iterator
///
/// The derived class should also implement conversion from compatible iterators. This allows,
/// for example, for non-const iterators to be converted to their const versions.
///
/// \tparam tDerived the derived iterator class, which must inherit from this IFacade
/// \tparam tCategory one of the standard iterator categories, e.g. `std::forward_iterator_tag`
/// \tparam tFacadeValue the (possibly const-qualified) type over which this iterator iterates
/// \tparam tReference the type which will be returned by `operator*`. It defaults to `tFacadeValue&`
/// \tparam tDifference for random iterators, the type for the distance between two iterators. It defaults to
///         `std::ptrdiff_t`
///
/// Examples of the use of this class can be seen in \ref ISplit or \ref ICounter.
///
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
template<typename tDerived,typename tCategory,typename tFacadeValue,
         typename tReference=tFacadeValue&,typename tDifference=std::ptrdiff_t>
class IFacade
{
    template<typename t1,typename t2,typename t3,typename t4,typename t5>
    friend class IFacade;

    tDerived &Derived()             {   return *static_cast<tDerived*>(this);           }
    const tDerived &Derived() const {   return *static_cast<const tDerived*>(this);     }

public:
    ///the difference type, as required by the standard
    typedef tDifference difference_type;
    ///the value type, as required by the standard
    typedef typename std::remove_cv<tFacadeValue>::type value_type;
    ///the pointer type, as required by the standard
    typedef typename std::add_pointer<tFacadeValue>::type pointer;
    ///the reference type, as required by the standard
    typedef tReference reference;
    ///the iterator category, as required by the standard
    typedef tCategory iterator_category;

private:
    typedef OConditional<std::is_same<iterator_category,std::input_iterator_tag>,
                         detail::CInputIteratorPostfixHelper<value_type>,tDerived> TPostfixReturnType;
    typedef OConditional<std::is_same<iterator_category,GCachingRandomAccessIterator>,
                         value_type,reference> TBracketReturnType;

public:
    ///returns a reference to the object currently pointed by the iterator
    reference operator*() const
    {
        return Derived().Dereference();
    }
    
    ///returns a pointer to the object returned by `operator*`
    pointer operator->() const
    {
        return std::addressof(*Derived());
    }
    
    ///increments the iterator
    tDerived& operator++()
    {
        Derived().Increment();
        return Derived();
    }

    ///increments the iterator (postfix version)

    ///For input iterators, it returns a proxy object which contains a copy of the pointed-to value before
    ///incrementing. For the rest of iterators, it returns a copy of the iterator before incrementing.
    TPostfixReturnType operator++(int)
    {
        TPostfixReturnType lTmp(Derived());
        operator++();
        return lTmp;
    }
    
    ///decrements the iterator
    
    ///The iterator must be at least bidirectional
    tDerived& operator--()
    {
        static_assert(QIteratorSupports<tDerived,std::bidirectional_iterator_tag>::value,
                      "iterator must be bidirectional to support operator--");
        Derived().Decrement();
        return Derived();
    }
    
    ///decrements the iterator (postfix version)
    
    ///The iterator must be at least bidirectional
    tDerived operator--(int)
    {
        static_assert(QIteratorSupports<tDerived,std::bidirectional_iterator_tag>::value,
                      "iterator must be bidirectional to support operator--");
        tDerived lTmp(Derived());
        operator--();
        return lTmp;
    }
    
    ///advances the iterator by \p pN positions

    ///The iterator must be random-access
    tDerived& operator+=(difference_type pN)
    {
        static_assert(QIteratorSupports<tDerived,std::random_access_iterator_tag>::value,
                      "iterator must be random-access to support operator+=");
        Derived().Advance(pN);
        return Derived();
    }
    
    ///retreats the iterator by \p pN positions
    
    ///The iterator must be random-access
    tDerived& operator-=(difference_type pN)
    {
        static_assert(QIteratorSupports<tDerived,std::random_access_iterator_tag>::value,
                      "iterator must be random-access to support operator-=");
        Derived().Advance(-pN);
        return Derived();
    }
    
    ///returns an iterator which points to \p pN positions beyond this one
    
    ///The iterator must be random-access
    tDerived operator+(difference_type pN) const
    {
        static_assert(QIteratorSupports<tDerived,std::random_access_iterator_tag>::value,
                      "iterator must be random-access to support operator+");
        tDerived lResult(Derived());
        return lResult+=pN;
    }

    ///returns an iterator which points to \p pN positions before this one
    
    ///The iterator must be random-access
    tDerived operator-(difference_type pN) const
    {
        static_assert(QIteratorSupports<tDerived,std::random_access_iterator_tag>::value,
                      "iterator must be random-access to support operator-");
        tDerived lResult(Derived());
        return lResult-=pN;
    }

    ///returns a reference to the object pointed to by `*this+pN`

    ///The iterator must be random-access
    TBracketReturnType operator[](difference_type pN) const
    {
        static_assert(QIteratorSupports<tDerived,std::random_access_iterator_tag>::value,
                      "iterator must be random-access to support operator[]");
        return *(Derived()+pN);
    }

    ///returns the distance between two compatible iterators

    ///Only available for random-access iterators or better
    template<typename tRHDerived,typename tRHCategory,typename tRHValue,typename tRHReference,typename tRHDifference>
    typename std::enable_if<std::is_convertible<tDerived,tRHDerived>::value||
                            std::is_convertible<tRHDerived,tDerived>::value,difference_type>::type
    operator-(const IFacade<tRHDerived,tRHCategory,tRHValue,tRHReference,tRHDifference> &pRH) const
    {
        static_assert(QIteratorSupports<tDerived,std::random_access_iterator_tag>::value,
                      "iterator must be random-access to support operator-");
        return pRH.DistanceToImpl(*this,std::is_convertible<tDerived,tRHDerived>());
    }

    ///returns `true` if the two iterators are equal
    template<typename tRHDerived,typename tRHCategory,typename tRHValue,typename tRHReference,typename tRHDifference>
    typename std::enable_if<std::is_convertible<tDerived,tRHDerived>::value||
                            std::is_convertible<tRHDerived,tDerived>::value,bool>::type
    operator==(const IFacade<tRHDerived,tRHCategory,tRHValue,tRHReference,tRHDifference> &pRH) const
    {
        return EqualImpl(pRH,std::is_convertible<tRHDerived,tDerived>());
    }

    ///returns `true` if the two iterators are different
    template<typename tRHDerived,typename tRHCategory,typename tRHValue,typename tRHReference,typename tRHDifference>
    typename std::enable_if<std::is_convertible<tDerived,tRHDerived>::value||
                            std::is_convertible<tRHDerived,tDerived>::value,bool>::type
    operator!=(const IFacade<tRHDerived,tRHCategory,tRHValue,tRHReference,tRHDifference> &pRH) const
    {
        return !EqualImpl(pRH,std::is_convertible<tRHDerived,tDerived>());
    }

private:
    template<typename tOther>
    bool EqualImpl(const tOther &pO,std::true_type) const
    {
        return Derived().Equal(pO.Derived());
    }

    template<typename tOther>
    bool EqualImpl(const tOther &pO,std::false_type) const
    {
        return pO.EqualImpl(*this,std::true_type());
    }
    
    template<typename tOther>
    difference_type DistanceToImpl(const tOther &pO,std::true_type) const
    {
        return Derived().DistanceTo(pO.Derived());
    }

    template<typename tOther>
    typename tOther::difference_type DistanceToImpl(const tOther &pO,std::false_type) const
    {
        return -pO.DistanceToImpl(*this,std::true_type());
    }
};

///@}

/*namespace buola*/ }

#endif
